package com.qubuyer.core.http

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.qubuyer.core.R
import com.qubuyer.core.ext.*
import com.tencent.bugly.crashreport.CrashReport
import kotlinx.coroutines.*
import retrofit2.HttpException
import java.net.UnknownHostException

/**
 * Author: linwg
 * Time: 2020/10/12 0012
 * Description:
 */
class RetrofitCall<T> constructor(
        var lifecycleOwner: LifecycleOwner?,
        private var scope: CoroutineScope,
        call: RetrofitCall<T>.() -> Unit,
) : LifecycleEventObserver {
    init {
        call()
        makeCall()
    }

    private var showDialog: (() -> Boolean)? = null
    private var showToast: (() -> Boolean)? = null
    private var timeout: (() -> Unit)? = null
    private var networkError: (() -> Unit)? = null
    private var success: ((T?) -> Unit)? = null
    private var readFromCache: (() -> T?)? = null
    private var over: (() -> Unit)? = null
    private var fail: ((T?, code: Int, msg: String?) -> Unit)? = null
    private var start: (() -> Unit)? = null
    private var consumeError: ((code: Int) -> Boolean)? = null
    private var api: ((suspend () -> HttpResult<T>))? = null
    private var job: Job? = null

    fun api(c: ((suspend () -> HttpResult<T>))?): RetrofitCall<T> {
        api = c
        return this
    }

    fun success(s: ((T?) -> Unit)?): RetrofitCall<T> {
        success = s
        return this
    }

    fun readFromCache(s: (() -> T?)?): RetrofitCall<T> {
        readFromCache = s
        return this
    }

    fun fail(f: ((T?, code: Int, msg: String?) -> Unit)?): RetrofitCall<T> {
        fail = f
        return this
    }

    fun onStart(s: (() -> Unit)?): RetrofitCall<T> {
        start = s
        return this
    }

    fun timeout(s: (() -> Unit)?): RetrofitCall<T> {
        timeout = s
        return this
    }

    fun over(s: (() -> Unit)?): RetrofitCall<T> {
        over = s
        return this
    }

    fun consumeError(s: ((code: Int) -> Boolean)?): RetrofitCall<T> {
        consumeError = s
        return this
    }

    fun showDialog(b: (() -> Boolean)): RetrofitCall<T> {
        showDialog = b
        return this
    }

    fun showToast(b: (() -> Boolean)): RetrofitCall<T> {
        showToast = b
        return this
    }

    fun networkError(b: (() -> Unit)?): RetrofitCall<T> {
        networkError = b
        return this
    }

    private fun makeCall() {
        if (!networkValid()) {
            if (networkError == null) {
                lifecycleOwner?.handlerNetworkInvalidMessage()
            }
            networkError?.invoke()
            over()
            return
        }
        lifecycleOwner?.lifecycle?.addObserver(this)
        job = scope.launch {
            withContext(Dispatchers.Main) {
                if (showDialog?.invoke() != false) {
                    lifecycleOwner?.showHttpRequestProgressDialog()
                }
                start?.invoke()
            }
            val cache = withContext(Dispatchers.IO) {
                readFromCache?.invoke()
            }
            if (cache != null) {
                lifecycleOwner?.hideHttpRequestProgressDialog()
                success?.invoke(cache)
            }
            var nullByTimeout = true
            val response = withContext(Dispatchers.IO) {
                withTimeoutOrNull(60000) {
                    try {
                        api?.invoke()
                    } catch (e: Exception) {
                        CrashReport.postCatchedException(e)
                        withContext(Dispatchers.Main) {
                            if (e is TokenRefreshFailException) {
                                lifecycleOwner?.handlerTokenInvalidMessage()
                                onFail(null, e.code, e.msg, true)
                            } else if (e is HttpException) {
                                val json = e.response()?.errorBody()?.string()
                                val fromJson = try {
                                    Gson().fromJson(json, HttpResult::class.java)
                                } catch (e: Exception) {
                                    null
                                }
                                if (fromJson != null) {
                                    onFail(null, fromJson.code(), fromJson.msg())
                                } else {
                                    onFail(null, e.code(), e.message)
                                }
                            } else if (e is UnknownHostException) {
                                onFail(null, HttpStatusCode.UNKNOWN_HOST, application.getString(R.string.unknown_host_tips) + e.message)
                            } else if (e is JsonSyntaxException) {
                                onFail(null, HttpStatusCode.JSON_PARSE_ERROR, application.getString(R.string.json_parse_error, e.message))
                            } else {
                                fail?.invoke(null, HttpStatusCode.UNKNOWN_ERROR, e.message)
                                lifecycleOwner?.toast(R.string.custom_toast_network_disconnected)
                            }
                        }
                        nullByTimeout = false
                        null
                    }
                }
            }
            withContext(Dispatchers.Main) {
                if (response == null) {
                    if (nullByTimeout) {
                        if (timeout == null) {
                            lifecycleOwner?.toast(R.string.network_time_out)
                        }
                        timeout?.invoke()
                    }
                    job?.cancel()
                } else {
                    if (response.ok()) {
                        success?.invoke(response.data())
                    } else if(response.isTokenException()){
                        lifecycleOwner?.handlerTokenInvalidMessage()
                        onFail(null, response.code(), response.msg(), true)
                    } else {
                        onFail(response.data(), response.code(), response.msg())
                    }
                }
                over()
            }
        }
    }

    private fun onFail(data: T?, code: Int, msg: String?, blockToast: Boolean = false) {
        fail?.invoke(data, code, msg)
        val hasConsume = consumeError?.invoke(code) ?: false
        if (!hasConsume && showToast?.invoke() != false && !blockToast) {
            lifecycleOwner?.toast(msg)
        }
    }

    private fun over() {
        over?.invoke()
        lifecycleOwner?.hideHttpRequestProgressDialog()
        job?.cancel()
        lifecycleOwner?.lifecycle?.removeObserver(this)
        networkError = null
        timeout = null
        api = null
        success = null
        over = null
        fail = null
        start = null
        lifecycleOwner = null
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            over()
        }
    }
}